﻿using System;
using System.Collections.Generic;
using System.Reflection;
using MugenInjection.Delegates;
using MugenInjection.Infrastructure;
using MugenInjection.Interface.Components;

namespace MugenInjection.Activators
{
    /// <summary>
    /// Represents the activator that use emit to activate object.
    /// </summary>
    public class EmitActivator : ActivatorBase
    {
        #region Fields

        private readonly CacheCollection<ConstructorInfo, ActivatorDelegate> _constructorActivators =
            new CacheCollection<ConstructorInfo, ActivatorDelegate>(3);

        private readonly CacheCollection<MemberInfo, Delegate> _methodActivators = new CacheCollection<MemberInfo, Delegate>(5);

        private readonly CacheCollection<MemberInfo, Delegate> _propertyActivators = new CacheCollection<MemberInfo, Delegate>(5);

        #endregion

        #region Overrides of ActivatorBase

        /// <summary>
        ///     Creates a new instance of the specified service.
        /// </summary>
        /// <param name="constructorInfo">
        ///     The specified <see cref="ConstructorInfo" />.
        /// </param>
        protected override object Activate(ConstructorInfo constructorInfo)
        {
            ActivatorDelegate constructorActivator = _constructorActivators[constructorInfo];
            if (constructorActivator == null)
            {
                constructorActivator = EmitReflectionAccessProvider.GetActivatorDelegate(constructorInfo, false);
                _constructorActivators[constructorInfo] = constructorActivator;
            }
            return constructorActivator.Invoke(CurrentContext.Injector
                                                             .Components.BindingActivatorComponent
                                                             .GetParameters(constructorInfo, CurrentContext));
        }

        /// <summary>
        ///     Injects values into properties, fields, methods in the specified object.
        /// </summary>
        /// <param name="targetType">
        ///     The specified target <see cref="Type" />.
        /// </param>
        /// <param name="target">The specified target for inject.</param>
        protected override void Inject<T>(Type targetType, ref T target)
        {
            Type attributeForInject = CurrentContext.Injector.Settings.AttributeForInject;
            if (!IsHasValuesToInject(targetType, attributeForInject)) return;
            IBindingActivatorComponent activatorComponent = CurrentContext.Injector.Components.BindingActivatorComponent;
            
            IList<PropertyInfo> propertiesToInject = GetPropertiesToInject(targetType, attributeForInject);
            if (propertiesToInject.Count > 0)
            {
                for (int i = 0; i < propertiesToInject.Count; i++)
                {
                    PropertyInfo propertyInfo = propertiesToInject[i];
                    var propertyActivator = _propertyActivators[propertyInfo] as SetterValueTypeDelegate<T>;
                    if (propertyActivator == null)
                    {
                        propertyActivator = EmitReflectionAccessProvider
                            .GetAssignMemberDelegate<SetterValueTypeDelegate<T>, T>(propertyInfo, true, false);
                        _propertyActivators[propertyInfo] = propertyActivator;
                    }
                    propertyActivator.Invoke(ref target, activatorComponent.GetParameter(propertyInfo, CurrentContext));
                }
            }


            IList<MethodInfo> methodsToInject = GetMethodsToInject(targetType, attributeForInject);
            if (methodsToInject.Count > 0)
            {
                for (int i = 0; i < methodsToInject.Count; i++)
                {
                    MethodInfo methodInfo = methodsToInject[i];
                    var methodActivator = _methodActivators[methodInfo] as InvokeMethodValueTypeDelegate<T>;
                    if (methodActivator == null)
                    {
                        methodActivator = EmitReflectionAccessProvider
                            .GetInvokeMethodDelegate<InvokeMethodValueTypeDelegate<T>, T>(methodInfo, true, false);
                        _methodActivators[methodInfo] = methodActivator;
                    }
                    methodActivator.Invoke(ref target, activatorComponent.GetParameters(methodInfo, CurrentContext));
                }
            }
        }

        /// <summary>
        /// Releases resources held by the object.
        /// </summary>
        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                _propertyActivators.Clear();
                _constructorActivators.Clear();
                _methodActivators.Clear();
            }
            base.Dispose(disposing);
        }

        #endregion
    }
}